package lab5task3;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 
 * Pool class of database connection pool implementation 
 * 
 * @author Bulaev Igor
 *
 * @param connSemaphore - connections semaphore
 * @param pool - array of connections
 * @param connCount - current connections available counter
 * @param ACQUIRE_TIMEOUT - timeout value for acquiring a connection
 * @param IDLE_TIMEOUT - idle value for connection
 *
 */

public class ConnectionPool {

	private Semaphore connSemaphore;
	private Connection[] pool;
	private int connCount;
	private static final long ACQUIRE_TIMEOUT = 5000;
	private static final long IDLE_TIMEOUT = 3000;

	public ConnectionPool(String url, String user, String password,
			int maxConnections) {
		
		/** Create array of connections, initialize connections, launch idle connections
		 *   collector daemon thread */
		
		connSemaphore = new Semaphore(maxConnections);
		pool = new Connection[maxConnections];
		for (int i = 0; i < maxConnections; i++) {
			pool[i] = new Connection();
		}
		connCount = maxConnections;
		idleCollectorinit();
	}
	
	private void idleCollectorinit() {
		Thread idleCollector = new Thread(new IdleCollector());
		idleCollector.setDaemon(true);
		idleCollector.start();
	}
	
	public Connection checkOut() throws InterruptedException, TimeoutException {
		if (connSemaphore.tryAcquire(ACQUIRE_TIMEOUT, TimeUnit.MILLISECONDS)) {
			synchronized(this) {
				connCount--;
				System.out.println("Connection acquired. Available: " + connCount);
				for (Connection connection : pool) {
					if (connection.getOwner() == null)	{
						connection.setOwner(Thread.currentThread());
						return connection;
					}
				}
			}
		} else {
			throw new TimeoutException();
		}
		return null;
	}

	public synchronized void checkIn(Connection connection) throws ConnectionClosedException {
		if(connection.getOwner() != null) {			
			connSemaphore.release();
			connCount++;
			System.out.println("Connection released. Available: " + connCount);
			connection.setOwner(null);
		} else throw new ConnectionClosedException();
	}
	
	/**
	 * 
	 * Method that gets invoked when idle connection collector is checking a
	 *  connection
	 * 	 
	 */
	
	private synchronized void checkConnection(Connection connection) {
		if (connection.getOwner() != null) {
			long idleTime = System.currentTimeMillis() - connection.getIdleTime();
			if (idleTime > IDLE_TIMEOUT) {
				connSemaphore.release();
				connCount++;
				connection.setOwner(null);
				System.out.println("Idle connection released. Available: " + connCount);
			}
		}
	}
	
	/**
	 * 
	 * Idle connection collector class for database connection pool
	 * implementation. Runs as daemon
	 *
	 */

	private class IdleCollector implements Runnable {
		
		private static final long CHECK_INTERVAL = 500;		
		
		@Override
		public void run() {
			while (true) {
				for (Connection conn : pool) {
					checkConnection(conn);
				}
				try {
					Thread.sleep(CHECK_INTERVAL);
				} catch (InterruptedException ie) {
					ie.printStackTrace();
				}
			}
		}

	}

}
